﻿#region License

// Another free, open solution by someone who gets it.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Linq.Expressions;
using System.Reflection;

using Patterns.Validation.Formatting;

namespace Patterns.Validation.Framework
{
	/// <summary>
	/// 	Provides framework-level validation extensions.
	/// </summary>
	public static class Extensions
	{
		private const string _invalidPropertyExpressionFormat = "Cannot use <{0}> when a property expression is expected.";
		private const string _nullPropertyExpressionFormat = "Expected a property expression, but found <null>.";

		/// <summary>
		/// 	Replaces all characters that might conflict with formatting placeholders and newlines with their escaped counterparts.
		/// </summary>
		public static string Escape(this string value)
		{
			return value.Replace("\"", "\\\"")
				.Replace("\n", @"\n")
				.Replace("\r", @"\r")
				.Replace("{", "{{")
				.Replace("}", "}}");
		}

		/// <summary>
		/// 	Gets the property info from the current expression.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "expression">The expression.</param>
		public static PropertyInfo GetPropertyInfo<T>(this Expression<Func<T, object>> expression)
		{
			if (ReferenceEquals(expression, null)) throw new NullReferenceException(_nullPropertyExpressionFormat);

			PropertyInfo propertyInfo = AttemptToGetPropertyInfoFromCastExpression(expression)
			                            ?? AttemptToGetPropertyInfoFromPropertyExpression(expression);

			if (propertyInfo == null) throw new ArgumentException(string.Format(_invalidPropertyExpressionFormat, expression.Body));

			return propertyInfo;
		}

		/// <summary>
		/// 	Finds the first index at which the <paramref name = "value" /> does not match the <paramref name = "expected" />
		/// 	string anymore, including the exact casing.
		/// </summary>
		public static int IndexOfFirstMismatch(this string value, string expected)
		{
			return IndexOfFirstMismatch(value, expected, StringComparison.CurrentCulture);
		}

		/// <summary>
		/// 	Finds the first index at which the <paramref name = "value" /> does not match the <paramref name = "expected" />
		/// 	string anymore, accounting for the specified <paramref name = "stringComparison" />.
		/// </summary>
		public static int IndexOfFirstMismatch(this string value, string expected, StringComparison stringComparison)
		{
			for (int index = 0; index < value.Length; index++)
			{
				if ((index >= expected.Length) || !value[index].ToString().Equals(expected[index].ToString(), stringComparison))
					return index;
			}

			return -1;
		}

		/// <summary>
		/// 	Gets the quoted three characters at the specified index of a string, including the index itself.
		/// </summary>
		public static string IndexedSegmentAt(this string value, int index)
		{
			int length = Math.Min(value.Length - index, 3);

			return string.Format("{0} (index {1})", Formatter.ToString(value.Substring(index, length)), index);
		}

		/// <summary>
		/// 	Determines whether the expected object is the same as, or equal to, the specified actual object.
		/// </summary>
		/// <param name = "actual">The actual.</param>
		/// <param name = "expected">The expected.</param>
		/// <returns>
		/// 	<c>true</c> if the two objects are the same or equal to one another; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsSameOrEqualTo(this object actual, object expected)
		{
			if (ReferenceEquals(actual, null) && ReferenceEquals(expected, null))
				return true;

			return !ReferenceEquals(actual, null) && actual.Equals(expected);
		}

		/// <summary>
		/// 	Determines whether the expected type is the same as, or inherits from, the specified actual type.
		/// </summary>
		/// <param name = "actualType">The actual type.</param>
		/// <param name = "expectedType">The expected type.</param>
		/// <returns>
		/// 	<c>true</c> if the two types are the same or part of an inheritance chain; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsSameOrInherits(this Type actualType, Type expectedType)
		{
			return (actualType == expectedType) || (expectedType.IsSubclassOf(actualType));
		}

		private static PropertyInfo AttemptToGetPropertyInfoFromPropertyExpression<T>(Expression<Func<T, object>> expression)
		{
			var memberExpression = expression.Body as MemberExpression;
			if (memberExpression != null)
				return (PropertyInfo) memberExpression.Member;

			return null;
		}

		private static PropertyInfo AttemptToGetPropertyInfoFromCastExpression<T>(Expression<Func<T, object>> expression)
		{
			var castExpression = expression.Body as UnaryExpression;
			if (castExpression != null)
				return (PropertyInfo) ((MemberExpression) castExpression.Operand).Member;

			return null;
		}
	}
}